﻿using System;
using System.Collections.Generic;
using System.Text;
using SkeinLib;
namespace System.Security.Cryptography
{
	public class HMACSkein512 : System.Security.Cryptography.KeyedHashAlgorithm
	{
        // NOTE:  This needs more work to make it conform to the zen/tao of the established Crypto .NET practices.
        // Such improvements include:
        // -exposing the State to the end user (can be keyed?, can't be keyed?, can be reused?)
        // -the ability to compute hashes in streams
        // -the ability to plug this into the other System.Security.Cryptography functions as a drop-in replacement to MD5/SHA

        private const int WORDWIDTH = 8;
        private const int DEFAULTHASHBITLENGTH = 512;
        internal byte[] _hashResult = null;
        internal UInt64[] _state = null;
        internal int _objectState = -1;
        internal byte[] _key = null;

        public new int HashSizeValue { get { return DEFAULTHASHBITLENGTH; } }

        public new byte[] Key
        {
            get
            {
                return (byte[])_key.Clone();
            }
            set
            {
                if (_objectState != 0)
                    throw new CryptographicException("An attempt was made to change the key while the object was not idle.");
                _key = (byte[])value.Clone();
                _state = SkeinCore.Initialize(WORDWIDTH, DEFAULTHASHBITLENGTH, _key);
            }
        }

        /// <summary>
        /// Indicates whether or not the object is idle, and by extension if the key can be changed (zero=idle, non-zero=in-process).
        /// </summary>
        public new int State { get { return _objectState; } }

        public override byte[] Hash { get { return _hashResult; } }

        public new byte[] ComputeHash(byte[] data)
        {
            if (_key == null)
                throw new InvalidOperationException("Key cannot be null.  Set the key before performing this operation.");
            if (data == null)
                throw new ArgumentNullException("data");
            _objectState = -1;
            SkeinCore.ProcessMessage(ref _state, data);
            _hashResult = SkeinCore.ProcessStandardOutput(ref _state, DEFAULTHASHBITLENGTH);
            return _hashResult;
        }

        public new byte[] ComputeHash(byte[] data, int indexStart, int length)
        {
            if (_key == null)
                throw new InvalidOperationException("Key cannot be null.  Set the key before performing this operation.");
            if (data == null)
                throw new ArgumentNullException("data");
            if (indexStart >= data.Length)
                throw new ArgumentOutOfRangeException("indexStart");
            if (length < 1)
                throw new ArgumentOutOfRangeException("length");
            if (indexStart + length > data.Length)
                throw new ArgumentOutOfRangeException("length");
            _objectState = -1;
            byte[] localData = new byte[length];
            Buffer.BlockCopy(data, indexStart, localData, 0, length);
            return ComputeHash(localData);
        }

        public new byte[] ComputeHash(System.IO.Stream data)
        {
            throw new NotImplementedException();
        }

        protected override void HashCore(byte[] data, int indexStart, int length)
        {
            throw new NotImplementedException();
        }

        protected override byte[] HashFinal()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Initialize the object in preperation for hashing.
        /// </summary>
        public override void Initialize()
        {
            this._objectState = 0;
            SkeinCore.ClearBytes(ref _key, true);
            SkeinCore.ClearWords(ref _state, true);
            SkeinCore.ClearBytes(ref _hashResult, true);
        }

    }
}
